#ifdef _WIN32
#include <windows.h>
#endif

#include <string>
#include <cstdlib>
#include "glee/GLee.h"
#include <GL/glu.h>
#include "player.h"
#include "gameworld.h"
#include "keyboardinterface.h"
#include "mouseinterface.h"
#include "spherecollider.h"
#include "landscape.h"
#include "glslshader.h"
#include "md2model.h"
#include "collider.h"
#include "AABBCollider.h"
#include "camera.h"
#include "Key.h"

using std::string;

const string PLAYER_MODEL = "data/models/Ogro/tris.md2";
const string PLAYER_TEXTURE = "data/models/Ogro/Ogrobase.tga";

Player::Player(GameWorld* const world):
Entity(world),
m_score(0),
m_pState(PLAYER_IDLE),
//m_position(Vector3()),
//m_velocity(Vector3()),
m_yaw(0.0f),
m_pitch(0.0f)
{
	mode = 0;
    //m_collider = new SphereCollider(this, 0.75f);
	m_collider = new AABBCollider(this,0.75,0.75,0.75);
	string vertexShader = (GLSLProgram::glsl130Supported())? "data/shaders/glsl1.30/model.vert" : "data/shaders/glsl1.20/model.vert";
    string fragmentShader = (GLSLProgram::glsl130Supported())? "data/shaders/glsl1.30/model.frag" : "data/shaders/glsl1.20/model.frag";
    m_model = new MD2Model(vertexShader, fragmentShader);
    m_model->setAnimation(Animation::IDLE);
	m_viewMode = PLAYER_MODE;
	m_world = world;
	m_selectedVertex = -1;
	m_selectedClump = -1;
	setVelocity(0.0f,0.0f,0.0f);
	setPosition(Vector3(0.0f,0.0f,0.0f));
	setGravity(true);
	for(int i=0;i<NUM_KEYS;i++){
		m_keys[i] = false;
	}
	setJumping(false);
}

Player::~Player()
{
    delete m_collider;
	delete m_model;
}

void normalize(Vector3* v)
{
	float len = sqrt((float)(v->x * v->x + v->y * v->y + v->z * v->z));
	v->x /= len;
	v->y /= len;
	v->z /= len;
}

void Player::getClick(Vector3* dir, Vector3* loc)
{
	int x = 0;
	int y = 0;
	getWorld()->getMouse()->getMousePos(x,y);

	double modelMatrix[16];
	double projMatrix[16];
	int viewport[4];

	double dx;
	double dy;
	double dz;
	double dcy;

	glGetIntegerv(GL_VIEWPORT, viewport);
	glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
	glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);

	dcy = viewport[3] - y;

	Vector3 close;
	Vector3 farr;

	gluUnProject(x,dcy, 0.0, modelMatrix,projMatrix,viewport,&dx,&dy,&dz);
	close.x = dx;
	close.y = dy;
	close.z = dz;

	gluUnProject(x,dcy, 1.0, modelMatrix,projMatrix,viewport,&dx,&dy,&dz);
	farr.x = dx;
	farr.y = dy;
	farr.z = dz;

	dir->x = farr.x - close.x;
	dir->y = farr.y - close.y;
	dir->z = farr.z - close.z;
	normalize(dir);

	getWorld()->getCameraPosition(loc);
}

void Player::calculateRay(Vector3* dir, Vector3* loc)
{
	getWorld()->getCameraPosition(loc);
	Vector3 lookat;
	getWorld()->getLookAtVector(&lookat);

	int x = 0;
	int y = 0;
	getWorld()->getMouse()->getMousePos(x,y);
	GLint viewport[4];
	glGetIntegerv(GL_VIEWPORT, viewport);
	
	int midx = viewport[2] / 2;
	int midy = viewport[3] / 2;
	/*
	x -= midx + lookat.x;
	y -= midy + lookat.y;
	int z = lookat.z;
	
	float len = sqrt((float)(x * x + y * y + z * z));
	float fx = x / len;
	float fy = y / len;
	float fz = z / len;
	
	dir->x = fx;
	dir->y = fy;
	dir->z = fz;*/

	x -= midx;
	y -= midy;

	Vector3 point;
	//convertTo3d(x,y,&point);

	Vector3 camPos;
	getWorld()->getCameraPosition(&camPos);

	point.x = camPos.x - point.x;
	point.y = camPos.y - point.y;
	point.z = camPos.z - point.z;

	normalize(&point);
	
	dir->x = point.x;
	dir->y = point.y;
	dir->z = point.z;

	dir->x = lookat.x;
	dir->y = lookat.y;
	dir->z = lookat.z;
	normalize(dir);

	int y2 = 2;
}

void Player::onPrepare(float dT)
{
	setVelocity(0.0f,getVelocity().y,0.0f);
	if (getWorld()->getKeyboard()->isKeyPressed(KC_v))
    {
       if (mode == 0) {
		   mode = 1;
		   m_pitch = 20;
	   }
	   else if (mode ==1) {
		   mode = 0;
		   m_pitch = 0;
	   }
    }

    if (getWorld()->getKeyboard()->isKeyHeldDown(KC_UP) || getWorld()->getKeyboard()->isKeyHeldDown(KC_w))
    {

		if(m_viewMode == PLAYER_MODE){
			float speed = 2.0f;
			if(getWorld()->getKeyboard()->isKeyHeldDown(KC_SHIFT)){
				speed *= 2.0f;
			}
			moveForward(speed * dT);
			if (m_pState != PLAYER_RUN)
			{
				m_pState = PLAYER_RUN;
				m_model->setAnimation(Animation::RUN);
			}
		}
		else{
			moveForwardFreeMode(2.0f * dT);
		}
    }
    else if (getWorld()->getKeyboard()->isKeyHeldDown(KC_DOWN) || getWorld()->getKeyboard()->isKeyHeldDown(KC_s))
    {
		if(m_viewMode == PLAYER_MODE){
			moveForward(-2.0f * dT);
			if (m_pState != PLAYER_RUN)
			{
				m_pState = PLAYER_RUN;
				m_model->setAnimation(Animation::RUN);
			}
		}
		else{
			moveForwardFreeMode(-2.0f * dT);
		}
    }
	else if (m_pState != PLAYER_IDLE)
	{
		m_pState = PLAYER_IDLE;
		m_model->setAnimation(Animation::IDLE);
	}
	else{
	}

    if (getWorld()->getKeyboard()->isKeyHeldDown(KC_RIGHT) || getWorld()->getKeyboard()->isKeyHeldDown(KC_d))
    {
        yaw(50.0f * dT);
    }
	if (getWorld()->getKeyboard()->isKeyHeldDown(KC_LEFT) || getWorld()->getKeyboard()->isKeyHeldDown(KC_a))
    {
        yaw(-50.0f * dT);
    }
	if (getWorld()->getKeyboard()->isKeyHeldDown(KC_e))
    {
        pitch(50.0f * dT);
    }
    if (getWorld()->getKeyboard()->isKeyHeldDown(KC_q))
    {
        pitch(-50.0f * dT);
    }

	if(getWorld()->getKeyboard()->isKeyHeldDown(KC_SPACE)){
		if(!getJumping()){
			setJumping(true);
			setVelocity(getVelocity().x,3.0f * dT,getVelocity().z);
		}
	}

	if(m_viewMode == PLAYER_MODE){
		if (getWorld()->getMouse()->isButtonPressed(0))
		{
			Entity* rocket = getWorld()->spawnEntity(ROCKET);
			Vector3 pos = getPosition();
			float cosYaw = cosf(degreesToRadians(m_yaw));
			float sinYaw = sinf(degreesToRadians(m_yaw));
			pos = Vector3(pos.x + cosYaw * 1.5f,pos.y + 0.01f,pos.z + sinYaw * 1.5f);
			rocket->setPosition(pos);
			rocket->setYaw(getYaw());

			//float cosYaw = cosf(degreesToRadians(m_yaw));
			//float sinYaw = sinf(degreesToRadians(m_yaw));
			float sinPitch = sinf(degreesToRadians(m_pitch));
			float cosPitch = cosf(degreesToRadians(m_pitch));

			const float speed = 1.0f + 15.0f * dT;
			Vector3 velocity;
			velocity.x = cosPitch * cosYaw * speed;
    		velocity.y = sinPitch * speed;
			velocity.z = cosPitch * sinYaw * speed;
			rocket->setVelocity(velocity.x,velocity.y,velocity.z);

			if (mode == 1) 
			{
				Vector3 pos = getPosition();
				float adjPitch = 1.0f - sinf(degreesToRadians(getPitch()));
				rocket->setPitch(-getPitch() + 11.0f + (3.0f*adjPitch));
			}
			else
			{
				rocket->setPitch(getPitch());
			}	
		}
	}

	
	else{
		if(getWorld()->getMouse()->isButtonPressed(0) && m_selectedVertex == -1)
		{
			Vector3 dir;
			Vector3 loc;
			//calculateRay(&dir, &loc);
			getClick(&dir,&loc);

			Terrain* terrain = getWorld()->getLandscape()->getTerrain();
			std::vector<Vertex> verts = terrain->getVertices();
			std::vector<TerrainClump*> clumps = terrain->getClumps();

			float closest = 10000000.0f;
			int bestClump = -1;
			int bestVertex = -1; 

			for(int i=0; i<clumps.size();i++){
				if(rayInSphere(clumps[i]->center,clumps[i]->radius,&loc,&dir)){
					float dx = loc.x - clumps[i]->center.x;
					float dy = loc.y - clumps[i]->center.y;
					float dz = loc.z - clumps[i]->center.z;
					float dist = sqrt(dx*dx + dy*dy + dz*dz);
					if(dist > 50.0f){
						continue;
					}
					for(int j = 0; j<clumps[i]->getIndices().size();j+=3){
						float dist = pointToRay(&verts[clumps[i]->getIndices()[j]], &loc, &dir);
						if(dist < closest){
							closest = dist;
							bestClump = i;
							bestVertex = j;

						}
					}
				}
			}
			m_selectedVertex = bestVertex;
			m_selectedClump = bestClump;
			

		}
		else if(!getWorld()->getMouse()->isButtonHeld(0) && !getWorld()->getMouse()->isButtonPressed(0)){
			m_selectedVertex = -1;
			m_selectedClump = -1;
		}
	}

	if(getWorld()->getKeyboard()->isKeyPressed(KC_t))
	{
		if(m_viewMode == FREE_MODE){
			m_viewMode = PLAYER_MODE;
			//m_world->getMouse()->showCursor(false);
		}
		else{
			m_viewMode = FREE_MODE;
			m_world->getMouse()->showCursor(true);
		}
	}
	if(m_viewMode != FREE_MODE){
		float x, y;
		getWorld()->getRelativeMousePos(x, y);

		yaw(float(x) * 40.0f * dT);
		pitch(float(y)* -40.0f * dT);

	
		//m_position.y -= 8.0f * dT;
	}
	

	int mouseX;
	int mouseY;
	getWorld()->getMouse()->getMousePos(mouseX,mouseY);

	if(m_selectedVertex != -1 && m_selectedClump != -1){
		float diffY = (m_lastMouseY - mouseY) / 100.0f;
		Terrain* terrain = getWorld()->getLandscape()->getTerrain();
		terrain->moveVertexY(m_selectedClump, m_selectedVertex, diffY);

					
	}

	m_lastMouseX = mouseX;
	m_lastMouseY = mouseY;

    float minX = getWorld()->getLandscape()->getTerrain()->getMinX() + 2.5f;
    float maxX = getWorld()->getLandscape()->getTerrain()->getMaxX() - 2.5f;
    float minZ = getWorld()->getLandscape()->getTerrain()->getMinZ() + 2.5f;
    float maxZ = getWorld()->getLandscape()->getTerrain()->getMaxZ() - 2.5f;

    if (m_position.x < minX) m_position.x = minX;
    if (m_position.x > maxX) m_position.x = maxX;
    if (m_position.z < minZ) m_position.z = minZ;
    if (m_position.z > maxZ) m_position.z = maxZ;
	getCollider()->setRadius(m_model->getRadius());

	m_model->update(dT);

	
}


float dot(Vector3* v1, Vector3* v2){
	return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z;
}



float Player::pointToRay(Vertex* v, Vector3* o, Vector3* dir)
{
	Vector3 pb2;
	Vector3 m2;
	m2.x = dir->x;
	m2.y = dir->y;
	m2.z = dir->z;

	pb2.x = v->x - o->x;
	pb2.y = v->y - o->y;
	pb2.z = v->z - o->z;

	float mpb2;
	mpb2 = dot(dir,&pb2);

	float mm2;
	mm2 = dot(dir,dir);

	float t2 = mpb2 / mm2;
	if(t2 <= 0){
		return 10000000.0f;
	}

	m2.x *= t2;
	m2.y *= t2;
	m2.z *= t2;

	m2.x += o->x;
	m2.y += o->y;
	m2.z += o->z;

	m2.x = v->x - m2.x;
	m2.y = v->y - m2.y;
	m2.z = v->z - m2.z;

	float dist = sqrt(m2.x * m2.x + m2.y * m2.y + m2.z * m2.z);

	return dist;
}

bool Player::rayInSphere(Vertex v, float radius, Vector3* origin, Vector3* dir)
{

	Vector3 m;
	m.x = origin->x - v.x;
	m.y = origin->y - v.y;
	m.z = origin->z - v.z;

	float d = sqrt((float)(m.x*m.x + m.y*m.y + m.z*m.z));
	if(d <= radius) return true;

	float b = dot(&m, dir);
	float c = dot(&m, &m) - (radius * radius);

	float det = b * b - c;

	if(det >= 0) return true;

	return false;
}

void Player::onRender() const
{
	glPushMatrix();
        Vector3 pos = getPosition();
        glTranslatef(pos.x, pos.y, pos.z);
		glRotatef(getPitch(), 0.0f, 0.0f, 0.0f);
        glRotatef(getYaw(), 0.0f, -1.0f, 0.0f);
        glBindTexture(GL_TEXTURE_2D, m_playerTextureID);
        m_model->render();
    glPopMatrix();

}

void Player::onPostRender()
{

}

bool Player::onInitialize()
{
	 bool result = m_model->load(PLAYER_MODEL);
    if (result)
    {
        if (!m_playerTexture.load(PLAYER_TEXTURE))
        {
            result = false;
        }
        else
        {
            glGenTextures(1, &m_playerTextureID);
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, m_playerTextureID);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

            gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8, m_playerTexture.getWidth(),
                              m_playerTexture.getHeight(), GL_RGB, GL_UNSIGNED_BYTE,
                              m_playerTexture.getImageData());
        }
    }

    return result;
}

void Player::onShutdown()
{

}

int Player::getMode()
{
	return mode;
}

void Player::yaw(const float val)
{
    m_yaw += val;

    if (m_yaw >= 360.0f) m_yaw -= 360.0f;
    if (m_yaw < 0.0f) m_yaw += 360.0f;
}

void Player::pitch(const float val)
{
    m_pitch += val;

	float TOP_PITCH_LIMIT = 75.0f;
	float BOT_PITCH_LIMIT = 75.0f;

	if (mode == 1)
		BOT_PITCH_LIMIT = 0.0f;
	
	if (m_pitch >= TOP_PITCH_LIMIT)
		{
			m_pitch = TOP_PITCH_LIMIT;
		}

	if (m_pitch <= -BOT_PITCH_LIMIT)
		{
			m_pitch = -BOT_PITCH_LIMIT;
		}

}

void Player::moveForward(const float speed)
{
    //Vector3 pos = getPosition();

    float cosYaw = cosf(degreesToRadians(m_yaw));
    float sinYaw = sinf(degreesToRadians(m_yaw));
    //pos.x += float(cosYaw)*speed;
   // pos.z += float(sinYaw)*speed;
	

    //setPosition(pos);

	Vector3 vel = getVelocity();
	vel.x = float(cosYaw)*speed;
	vel.z = float(sinYaw)*speed;
	setVelocity(vel.x,vel.y,vel.z);
}

void Player::moveForwardFreeMode(const float speed)
{
	Vector3 pos = getPosition();
	float cosYaw = cosf(degreesToRadians(m_yaw));
    float sinYaw = sinf(degreesToRadians(m_yaw));
	float cosPitch = sinf(degreesToRadians(m_pitch));
    pos.x += float(cosYaw)*speed;
    pos.z += float(sinYaw)*speed;
	pos.y += float(cosPitch)*speed;

    setPosition(pos);
}

void Player::onCollision(Entity* entity)
{
	EntityType et = entity->getType();
	if(et == KEY){
		Key* key = (Key*)entity;
		m_keys[key->getKeyNumber()] = true;
	}

}